package com.hqd.ch03.v51.boot.autoconfigure.web;

import com.hqd.ch03.v51.boot.context.properties.ConfigurationProperties;
import com.hqd.ch03.v51.boot.context.properties.NestedConfigurationProperty;
import com.hqd.ch03.v51.boot.convert.DurationUnit;
import com.hqd.ch03.v51.boot.web.server.Shutdown;
import com.hqd.ch03.v51.boot.web.server.*;
import com.hqd.ch03.v51.boot.web.servlet.server.Encoding;
import com.hqd.ch03.v51.boot.web.servlet.server.Jsp;
import com.hqd.ch03.v51.boot.web.servlet.server.Session;
import com.hqd.ch03.v51.utils.StringUtils;
import com.hqd.ch03.v51.utils.unit.DataSize;

import java.io.File;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 服务器配置类
 */
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    /**
     * 发生异常后配置
     */
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    @NestedConfigurationProperty
    private final Compression compression = new Compression();
    @NestedConfigurationProperty
    private final Http2 http2 = new Http2();
    private final Servlet servlet = new Servlet();
    private final Reactive reactive = new Reactive();
    private final Tomcat tomcat = new Tomcat();
    /**
     * http端口
     */
    private Integer port;
    /**
     * 服务器应该绑定到的网络地址
     */
    private InetAddress address;
    /**
     * 处理X-Forwarded-*标头的策略
     */
    private ForwardHeadersStrategy forwardHeadersStrategy;
    /**
     * 用于服务器响应头的值（如果为空，则不发送头）
     */
    private String serverHeader;
    /**
     * HTTP消息头的最大大小
     */
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);
    /**
     * 服务器将支持的关闭类型
     */
    private Shutdown shutdown = Shutdown.IMMEDIATE;
    @NestedConfigurationProperty
    private Ssl ssl;

    public Integer getPort() {
        return this.port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public InetAddress getAddress() {
        return this.address;
    }

    public void setAddress(InetAddress address) {
        this.address = address;
    }

    public String getServerHeader() {
        return this.serverHeader;
    }

    public void setServerHeader(String serverHeader) {
        this.serverHeader = serverHeader;
    }

    public DataSize getMaxHttpHeaderSize() {
        return this.maxHttpHeaderSize;
    }

    public void setMaxHttpHeaderSize(DataSize maxHttpHeaderSize) {
        this.maxHttpHeaderSize = maxHttpHeaderSize;
    }

    public Shutdown getShutdown() {
        return this.shutdown;
    }

    public void setShutdown(Shutdown shutdown) {
        this.shutdown = shutdown;
    }

    public ErrorProperties getError() {
        return this.error;
    }

    public Ssl getSsl() {
        return this.ssl;
    }

    public void setSsl(Ssl ssl) {
        this.ssl = ssl;
    }

    public Compression getCompression() {
        return this.compression;
    }

    public Http2 getHttp2() {
        return this.http2;
    }

    public Servlet getServlet() {
        return this.servlet;
    }

    public Reactive getReactive() {
        return this.reactive;
    }

    public Tomcat getTomcat() {
        return this.tomcat;
    }

    public ForwardHeadersStrategy getForwardHeadersStrategy() {
        return this.forwardHeadersStrategy;
    }

    public void setForwardHeadersStrategy(ForwardHeadersStrategy forwardHeadersStrategy) {
        this.forwardHeadersStrategy = forwardHeadersStrategy;
    }

    /**
     * 支持转发报头的策略
     */
    public enum ForwardHeadersStrategy {

        /**
         * 使用底层容器对转发头文件的原生支持
         */
        NATIVE,

        /**
         * 使用Spring的支持来处理转发的标头
         */
        FRAMEWORK,

        /**
         * 忽略X-Forwarded-*标头
         */
        NONE

    }

    /**
     * Servlet服务器属性
     */
    public static class Servlet {

        /**
         * Servlet上下文初始化参数
         */
        private final Map<String, String> contextParameters = new HashMap<>();
        @NestedConfigurationProperty
        private final Encoding encoding = new Encoding();
        @NestedConfigurationProperty
        private final Jsp jsp = new Jsp();
        @NestedConfigurationProperty
        private final Session session = new Session();
        /**
         * 应用程序的上下文路径
         */
        private String contextPath;
        /**
         * 显示应用程序的名称
         */
        private String applicationDisplayName = "application";
        /**
         * 是否向容器注册默认Servlet
         */
        private boolean registerDefaultServlet = false;

        public String getContextPath() {
            return this.contextPath;
        }

        public void setContextPath(String contextPath) {
            this.contextPath = cleanContextPath(contextPath);
        }

        private String cleanContextPath(String contextPath) {
            String candidate = StringUtils.trimWhitespace(contextPath);
            if (StringUtils.hasText(candidate) && candidate.endsWith("/")) {
                return candidate.substring(0, candidate.length() - 1);
            }
            return candidate;
        }

        public String getApplicationDisplayName() {
            return this.applicationDisplayName;
        }

        public void setApplicationDisplayName(String displayName) {
            this.applicationDisplayName = displayName;
        }

        public boolean isRegisterDefaultServlet() {
            return this.registerDefaultServlet;
        }

        public void setRegisterDefaultServlet(boolean registerDefaultServlet) {
            this.registerDefaultServlet = registerDefaultServlet;
        }

        public Map<String, String> getContextParameters() {
            return this.contextParameters;
        }

        public Encoding getEncoding() {
            return this.encoding;
        }

        public Jsp getJsp() {
            return this.jsp;
        }

        public Session getSession() {
            return this.session;
        }

    }

    /**
     * 响应式服务器属性
     */
    public static class Reactive {

        private final Session session = new Session();

        public Session getSession() {
            return this.session;
        }

        public static class Session {

            @NestedConfigurationProperty
            private final Cookie cookie = new Cookie();
            /**
             * 会话超时时间。如果未指定持续时间单位，默认为秒
             */
            @DurationUnit(ChronoUnit.SECONDS)
            private Duration timeout = Duration.ofMinutes(30);

            public Duration getTimeout() {
                return this.timeout;
            }

            public void setTimeout(Duration timeout) {
                this.timeout = timeout;
            }

            public Cookie getCookie() {
                return this.cookie;
            }

        }

    }

    /**
     * Tomcat属性
     */
    public static class Tomcat {

        /**
         * 接入日志配置
         */
        private final Accesslog accesslog = new Accesslog();

        /**
         * 线程相关配置
         */
        private final Threads threads = new Threads();
        /**
         * 静态资源配置
         */
        private final Resource resource = new Resource();
        /**
         * Modeler MBean Registry配置
         */
        private final Mbeanregistry mbeanregistry = new Mbeanregistry();
        /**
         * Remote Ip Valve configuration.
         */
        private final Remoteip remoteip = new Remoteip();
        /**
         * Tomcat base directory. If not specified, a temporary directory is used.
         */
        private File basedir;
        /**
         * Delay between the invocation of backgroundProcess methods. If a duration suffix
         * is not specified, seconds will be used.
         */
        @DurationUnit(ChronoUnit.SECONDS)
        private Duration backgroundProcessorDelay = Duration.ofSeconds(10);
        /**
         * Maximum size of the form content in any HTTP post request.
         */
        private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);
        /**
         * Maximum amount of request body to swallow.
         */
        private DataSize maxSwallowSize = DataSize.ofMegabytes(2);
        /**
         * Whether requests to the context root should be redirected by appending a / to
         * the path. When using SSL terminated at a proxy, this property should be set to
         * false.
         */
        private Boolean redirectContextRoot = true;
        /**
         * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
         * will use relative or absolute redirects.
         */
        private boolean useRelativeRedirects;
        /**
         * Character encoding to use to decode the URI.
         */
        private Charset uriEncoding = StandardCharsets.UTF_8;
        /**
         * Maximum number of connections that the server accepts and processes at any
         * given time. Once the limit has been reached, the operating system may still
         * accept connections based on the "acceptCount" property.
         */
        private int maxConnections = 8192;
        /**
         * Maximum queue length for incoming connection requests when all possible request
         * processing threads are in use.
         */
        private int acceptCount = 100;
        /**
         * Maximum number of idle processors that will be retained in the cache and reused
         * with a subsequent request. When set to -1 the cache will be unlimited with a
         * theoretical maximum size equal to the maximum number of connections.
         */
        private int processorCache = 200;
        /**
         * Time to wait for another HTTP request before the connection is closed. When not
         * set the connectionTimeout is used. When set to -1 there will be no timeout.
         */
        private Duration keepAliveTimeout;
        /**
         * Maximum number of HTTP requests that can be pipelined before the connection is
         * closed. When set to 0 or 1, keep-alive and pipelining are disabled. When set to
         * -1, an unlimited number of pipelined or keep-alive requests are allowed.
         */
        private int maxKeepAliveRequests = 100;
        /**
         * Comma-separated list of additional patterns that match jars to ignore for TLD
         * scanning. The special '?' and '*' characters can be used in the pattern to
         * match one and only one character and zero or more characters respectively.
         */
        private List<String> additionalTldSkipPatterns = new ArrayList<>();
        /**
         * Comma-separated list of additional unencoded characters that should be allowed
         * in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.
         */
        private List<Character> relaxedPathChars = new ArrayList<>();
        /**
         * Comma-separated list of additional unencoded characters that should be allowed
         * in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed.
         */
        private List<Character> relaxedQueryChars = new ArrayList<>();
        /**
         * Amount of time the connector will wait, after accepting a connection, for the
         * request URI line to be presented.
         */
        private Duration connectionTimeout;
        /**
         * Whether to reject requests with illegal header names or values.
         */
        private boolean rejectIllegalHeader = true;

        public DataSize getMaxHttpFormPostSize() {
            return this.maxHttpFormPostSize;
        }

        public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
            this.maxHttpFormPostSize = maxHttpFormPostSize;
        }

        public Accesslog getAccesslog() {
            return this.accesslog;
        }

        public Threads getThreads() {
            return this.threads;
        }

        public Duration getBackgroundProcessorDelay() {
            return this.backgroundProcessorDelay;
        }

        public void setBackgroundProcessorDelay(Duration backgroundProcessorDelay) {
            this.backgroundProcessorDelay = backgroundProcessorDelay;
        }

        public File getBasedir() {
            return this.basedir;
        }

        public void setBasedir(File basedir) {
            this.basedir = basedir;
        }

        public Boolean getRedirectContextRoot() {
            return this.redirectContextRoot;
        }

        public void setRedirectContextRoot(Boolean redirectContextRoot) {
            this.redirectContextRoot = redirectContextRoot;
        }

        public boolean isUseRelativeRedirects() {
            return this.useRelativeRedirects;
        }

        public void setUseRelativeRedirects(boolean useRelativeRedirects) {
            this.useRelativeRedirects = useRelativeRedirects;
        }

        public Charset getUriEncoding() {
            return this.uriEncoding;
        }

        public void setUriEncoding(Charset uriEncoding) {
            this.uriEncoding = uriEncoding;
        }

        public int getMaxConnections() {
            return this.maxConnections;
        }

        public void setMaxConnections(int maxConnections) {
            this.maxConnections = maxConnections;
        }

        public DataSize getMaxSwallowSize() {
            return this.maxSwallowSize;
        }

        public void setMaxSwallowSize(DataSize maxSwallowSize) {
            this.maxSwallowSize = maxSwallowSize;
        }

        public int getAcceptCount() {
            return this.acceptCount;
        }

        public void setAcceptCount(int acceptCount) {
            this.acceptCount = acceptCount;
        }

        public int getProcessorCache() {
            return this.processorCache;
        }

        public void setProcessorCache(int processorCache) {
            this.processorCache = processorCache;
        }

        public Duration getKeepAliveTimeout() {
            return this.keepAliveTimeout;
        }

        public void setKeepAliveTimeout(Duration keepAliveTimeout) {
            this.keepAliveTimeout = keepAliveTimeout;
        }

        public int getMaxKeepAliveRequests() {
            return this.maxKeepAliveRequests;
        }

        public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
            this.maxKeepAliveRequests = maxKeepAliveRequests;
        }

        public List<String> getAdditionalTldSkipPatterns() {
            return this.additionalTldSkipPatterns;
        }

        public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) {
            this.additionalTldSkipPatterns = additionalTldSkipPatterns;
        }

        public List<Character> getRelaxedPathChars() {
            return this.relaxedPathChars;
        }

        public void setRelaxedPathChars(List<Character> relaxedPathChars) {
            this.relaxedPathChars = relaxedPathChars;
        }

        public List<Character> getRelaxedQueryChars() {
            return this.relaxedQueryChars;
        }

        public void setRelaxedQueryChars(List<Character> relaxedQueryChars) {
            this.relaxedQueryChars = relaxedQueryChars;
        }

        public Duration getConnectionTimeout() {
            return this.connectionTimeout;
        }

        public void setConnectionTimeout(Duration connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
        }

        public boolean isRejectIllegalHeader() {
            return this.rejectIllegalHeader;
        }

        public void setRejectIllegalHeader(boolean rejectIllegalHeader) {
            this.rejectIllegalHeader = rejectIllegalHeader;
        }

        public Resource getResource() {
            return this.resource;
        }

        public Mbeanregistry getMbeanregistry() {
            return this.mbeanregistry;
        }

        public Remoteip getRemoteip() {
            return this.remoteip;
        }

        /**
         * Tomcat access log properties.
         */
        public static class Accesslog {

            /**
             * Log file name prefix.
             */
            protected String prefix = "access_log";
            /**
             * Enable access log.
             */
            private boolean enabled = false;
            /**
             * Whether logging of the request will only be enabled if
             * "ServletRequest.getAttribute(conditionIf)" does not yield null.
             */
            private String conditionIf;
            /**
             * Whether logging of the request will only be enabled if
             * "ServletRequest.getAttribute(conditionUnless)" yield null.
             */
            private String conditionUnless;
            /**
             * Format pattern for access logs.
             */
            private String pattern = "common";
            /**
             * Directory in which log files are created. Can be absolute or relative to
             * the Tomcat base dir.
             */
            private String directory = "logs";
            /**
             * Log file name suffix.
             */
            private String suffix = ".log";

            /**
             * Character set used by the log file. Default to the system default character
             * set.
             */
            private String encoding;

            /**
             * Locale used to format timestamps in log entries and in log file name
             * suffix. Default to the default locale of the Java process.
             */
            private String locale;

            /**
             * Whether to check for log file existence so it can be recreated it if an
             * external process has renamed it.
             */
            private boolean checkExists = false;

            /**
             * Whether to enable access log rotation.
             */
            private boolean rotate = true;

            /**
             * Whether to defer inclusion of the date stamp in the file name until rotate
             * time.
             */
            private boolean renameOnRotate = false;

            /**
             * Number of days to retain the access log files before they are removed.
             */
            private int maxDays = -1;

            /**
             * Date format to place in the log file name.
             */
            private String fileDateFormat = ".yyyy-MM-dd";

            /**
             * Whether to use IPv6 canonical representation format as defined by RFC 5952.
             */
            private boolean ipv6Canonical = false;

            /**
             * Set request attributes for the IP address, Hostname, protocol, and port
             * used for the request.
             */
            private boolean requestAttributesEnabled = false;

            /**
             * Whether to buffer output such that it is flushed only periodically.
             */
            private boolean buffered = true;

            public boolean isEnabled() {
                return this.enabled;
            }

            public void setEnabled(boolean enabled) {
                this.enabled = enabled;
            }

            public String getConditionIf() {
                return this.conditionIf;
            }

            public void setConditionIf(String conditionIf) {
                this.conditionIf = conditionIf;
            }

            public String getConditionUnless() {
                return this.conditionUnless;
            }

            public void setConditionUnless(String conditionUnless) {
                this.conditionUnless = conditionUnless;
            }

            public String getPattern() {
                return this.pattern;
            }

            public void setPattern(String pattern) {
                this.pattern = pattern;
            }

            public String getDirectory() {
                return this.directory;
            }

            public void setDirectory(String directory) {
                this.directory = directory;
            }

            public String getPrefix() {
                return this.prefix;
            }

            public void setPrefix(String prefix) {
                this.prefix = prefix;
            }

            public String getSuffix() {
                return this.suffix;
            }

            public void setSuffix(String suffix) {
                this.suffix = suffix;
            }

            public String getEncoding() {
                return this.encoding;
            }

            public void setEncoding(String encoding) {
                this.encoding = encoding;
            }

            public String getLocale() {
                return this.locale;
            }

            public void setLocale(String locale) {
                this.locale = locale;
            }

            public boolean isCheckExists() {
                return this.checkExists;
            }

            public void setCheckExists(boolean checkExists) {
                this.checkExists = checkExists;
            }

            public boolean isRotate() {
                return this.rotate;
            }

            public void setRotate(boolean rotate) {
                this.rotate = rotate;
            }

            public boolean isRenameOnRotate() {
                return this.renameOnRotate;
            }

            public void setRenameOnRotate(boolean renameOnRotate) {
                this.renameOnRotate = renameOnRotate;
            }

            public int getMaxDays() {
                return this.maxDays;
            }

            public void setMaxDays(int maxDays) {
                this.maxDays = maxDays;
            }

            public String getFileDateFormat() {
                return this.fileDateFormat;
            }

            public void setFileDateFormat(String fileDateFormat) {
                this.fileDateFormat = fileDateFormat;
            }

            public boolean isIpv6Canonical() {
                return this.ipv6Canonical;
            }

            public void setIpv6Canonical(boolean ipv6Canonical) {
                this.ipv6Canonical = ipv6Canonical;
            }

            public boolean isRequestAttributesEnabled() {
                return this.requestAttributesEnabled;
            }

            public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
                this.requestAttributesEnabled = requestAttributesEnabled;
            }

            public boolean isBuffered() {
                return this.buffered;
            }

            public void setBuffered(boolean buffered) {
                this.buffered = buffered;
            }

        }

        /**
         * Tomcat thread properties.
         */
        public static class Threads {

            /**
             * Maximum amount of worker threads.
             */
            private int max = 200;

            /**
             * Minimum amount of worker threads.
             */
            private int minSpare = 10;

            public int getMax() {
                return this.max;
            }

            public void setMax(int max) {
                this.max = max;
            }

            public int getMinSpare() {
                return this.minSpare;
            }

            public void setMinSpare(int minSpare) {
                this.minSpare = minSpare;
            }

        }

        /**
         * Tomcat static resource properties.
         */
        public static class Resource {

            /**
             * Whether static resource caching is permitted for this web application.
             */
            private boolean allowCaching = true;

            /**
             * Time-to-live of the static resource cache.
             */
            private Duration cacheTtl;

            public boolean isAllowCaching() {
                return this.allowCaching;
            }

            public void setAllowCaching(boolean allowCaching) {
                this.allowCaching = allowCaching;
            }

            public Duration getCacheTtl() {
                return this.cacheTtl;
            }

            public void setCacheTtl(Duration cacheTtl) {
                this.cacheTtl = cacheTtl;
            }

        }

        public static class Mbeanregistry {

            /**
             * Whether Tomcat's MBean Registry should be enabled.
             */
            private boolean enabled;

            public boolean isEnabled() {
                return this.enabled;
            }

            public void setEnabled(boolean enabled) {
                this.enabled = enabled;
            }

        }

        public static class Remoteip {

            /**
             * Regular expression that matches proxies that are to be trusted.
             */
            private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
                    + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
                    + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
                    + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
                    + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
                    + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" //
                    + "0:0:0:0:0:0:0:1|::1";

            /**
             * Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
             */
            private String protocolHeader;

            /**
             * Value of the protocol header indicating whether the incoming request uses
             * SSL.
             */
            private String protocolHeaderHttpsValue = "https";

            /**
             * Name of the HTTP header from which the remote host is extracted.
             */
            private String hostHeader = "X-Forwarded-Host";

            /**
             * Name of the HTTP header used to override the original port value.
             */
            private String portHeader = "X-Forwarded-Port";

            /**
             * Name of the HTTP header from which the remote IP is extracted. For
             * instance, 'X-FORWARDED-FOR'.
             */
            private String remoteIpHeader;

            public String getInternalProxies() {
                return this.internalProxies;
            }

            public void setInternalProxies(String internalProxies) {
                this.internalProxies = internalProxies;
            }

            public String getProtocolHeader() {
                return this.protocolHeader;
            }

            public void setProtocolHeader(String protocolHeader) {
                this.protocolHeader = protocolHeader;
            }

            public String getProtocolHeaderHttpsValue() {
                return this.protocolHeaderHttpsValue;
            }

            public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
                this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
            }

            public String getHostHeader() {
                return this.hostHeader;
            }

            public void setHostHeader(String hostHeader) {
                this.hostHeader = hostHeader;
            }

            public String getPortHeader() {
                return this.portHeader;
            }

            public void setPortHeader(String portHeader) {
                this.portHeader = portHeader;
            }

            public String getRemoteIpHeader() {
                return this.remoteIpHeader;
            }

            public void setRemoteIpHeader(String remoteIpHeader) {
                this.remoteIpHeader = remoteIpHeader;
            }

        }

    }

}
